﻿// Original author contact info: Owen Emlen (owene_1998@yahoo.com)
// Note: other individuals may also have contributed to this code
// Project hosted on CodePlex.com as of 1/10/2009 at http://www.codeplex.com/EmlenMud
using System.Runtime.Serialization;
using System;
using System.Xml.Serialization;
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading;
using System.Collections.Specialized;
#if USE_HYPER
using Hyper.ComponentModel;
#endif

namespace BrainTechLLC.ThreadSafeObjects
{
    /// <summary>
    /// Special case Multi-level dictionary that is indexed by a sequence of integers
    /// </summary>
    /// <typeparam name="TItem"></typeparam>
	[Serializable]
	[DataContract]
	public class MultiLevelCustom<TItem> : INotifyCollectionChanged
	{
		[DataMember]
		public Dictionary<int, MultiLevelCustom<TItem>> ItemLookup;
		[DataMember]
		public Dictionary<int, TItem> TerminatingItems;

		[NonSerialized, XmlIgnore]
		public Cached<List<KeyValuePair<List<int>, TItem>>> AllTerminatingItemsListCache;

		public int ItemCount
		{
			get
			{
				int count = 0;

				count += (TerminatingItems == null) ? 0 : TerminatingItems.Count;

				if (ItemLookup != null)
					ItemLookup.ForEach((dict) => count += dict.Value.ItemCount);

				return count;
			}
		}

		public int MaxDepth(int startDepth)
		{
			int max = startDepth;
			if (ItemLookup != null)
			{
				foreach (var kvp in ItemLookup)
				{
					if (kvp.Value != null)
					{
						int depth = kvp.Value.MaxDepth(startDepth + 1);
						if (depth > max) max = depth;
					}
				}
			}
			return max;
		}

		public void AtLevel(int level, int currentLevel, Action<Dictionary<int, TItem>> action, Action<int> levelTooDeep)
		{
			if (currentLevel == level && TerminatingItems != null)
			{
				action(TerminatingItems);
			}
			else
			{
				if (ItemLookup == null)
				{
					levelTooDeep(currentLevel + 1);
				}
				else
				{
					foreach (var v in ItemLookup)
					{
						if (v.Value != null)
							v.Value.AtLevel(level, currentLevel + 1, action, levelTooDeep);
					}
				}
			}
		}

		public Dictionary<List<int>, TItem> AllTerminatingItems
		{
			get { return ReadTerminatingItemsRecursive(new List<int>()); }
		}

        public List<TItem> AllItems
        {
            get
            {
                List<TItem> items = new List<TItem>();
                var all = AllTerminatingItemsList;
                for (int n = 0; n < all.Count; n++)
                {
                    if (all[n].Value != null)
                        items.Add(all[n].Value);
                }
                return items;
            }
        }

		public List<KeyValuePair<List<int>, TItem>> AllTerminatingItemsList
		{
			get
			{
				if (AllTerminatingItemsListCache == null)
				{
					Interlocked.CompareExchange<Cached<List<KeyValuePair<List<int>, TItem>>>>(ref AllTerminatingItemsListCache,
						 new Cached<List<KeyValuePair<List<int>, TItem>>>(delegate(object parameterIn, out List<KeyValuePair<List<int>, TItem>> results, object[] args)
							  {
								  List<KeyValuePair<List<int>, TItem>> valuePairs = new List<KeyValuePair<List<int>, TItem>>();
								  Dictionary<List<int>, TItem> items = ReadTerminatingItemsRecursive(new List<int>());

								  foreach (KeyValuePair<List<int>, TItem> item in items)
									  valuePairs.Add(item);

								  results = valuePairs;
								  return true;
							  }), null);
				}
				return AllTerminatingItemsListCache.GetItem(null, null);
			}
		}

		public bool ContainsValue(TItem item)
		{
			if (TerminatingItems != null)
			{
				bool contains = TerminatingItems.ContainsValue(item);

				if (contains)
					return true;
			}

			if (ItemLookup != null)
			{
				foreach (var v in ItemLookup)
				{
					bool contains = v.Value.ContainsValue(item);

					if (contains)
						return true;
				}
			}

			return false;
		}

		public Dictionary<List<int>, TItem> ReadTerminatingItemsRecursive(List<int> keysSoFar)
		{
			Dictionary<List<int>, TItem> allItems = new Dictionary<List<int>, TItem>();

			TerminatingItems.ForEachIfNotNull((item) =>
				 {
					 List<int> keysSoFarCopy = new List<int>(keysSoFar);
					 keysSoFarCopy.Add(item.Key);
					 allItems.Add(keysSoFarCopy, item.Value);
				 });

			if (ItemLookup != null)
			{
				foreach (KeyValuePair<int, MultiLevelCustom<TItem>> item in ItemLookup)
				{
					List<int> keysSoFarCopy = new List<int>(keysSoFar);
					keysSoFarCopy.Add(item.Key);
					Dictionary<List<int>, TItem> subItems = item.Value.ReadTerminatingItemsRecursive(keysSoFarCopy);
					subItems.ForEachIfNotNull(subItem => allItems.Add(subItem.Key, subItem.Value));
				}
			}

			return allItems;
		}

		public bool RemoveTerminatingItem(int keyLevel, int maxLevel, params int[] keys)
		{
			int key = keys[keyLevel];

			if (keyLevel == maxLevel - 1)
			{
				TItem item = default(TItem);
				if (TerminatingItems != null && TerminatingItems.TryGetValue(key, out item))
				{
					// possible race condition here
					TerminatingItems.Remove(key);
					AllTerminatingItemsListCache.SafeMarkDirty();
					OnNotifyCollectionChanged(NotifyCollectionChangedAction.Remove, item);
					return true;
				}

				return false;
			}
			else
			{
				if (ItemLookup != null)
				{
					MultiLevelCustom<TItem> dictionary;

					if (ItemLookup.TryGetValue(key, out dictionary))
					{
						bool found = dictionary.RemoveTerminatingItem(keyLevel + 1, maxLevel, keys);

						if (found)
							AllTerminatingItemsListCache.SafeMarkDirty();

						return found;
					}
				}

				return false;
			}
		}

		public bool RemoveTerminatingItem(int keyLevel, int maxLevel, List<int> keys)
		{
			int key = keys[keyLevel];

			if (keyLevel == maxLevel - 1)
			{
				TItem item = default(TItem);
				if (TerminatingItems != null && TerminatingItems.TryGetValue(key, out item))
				{
					// possible race condition here
					TerminatingItems.Remove(key);
					AllTerminatingItemsListCache.SafeMarkDirty();
					OnNotifyCollectionChanged(NotifyCollectionChangedAction.Remove, item);
					return true;
				}

				return false;
			}
			else
			{
				if (ItemLookup != null)
				{
					MultiLevelCustom<TItem> dictionary;

					if (ItemLookup.TryGetValue(key, out dictionary))
					{
						bool found = dictionary.RemoveTerminatingItem(keyLevel + 1, maxLevel, keys);

						if (found)
							AllTerminatingItemsListCache.SafeMarkDirty();

						return found;
					}
				}

				return false;
			}
		}


		public bool RemoveTerminatingItem(params int[] keys)
		{
			int keysLength = keys.Length;

			if (keysLength == 0)
			{
				keys = new int[] { default(int) };
				keysLength = 1;
				//throw new Exception("Must have at least 1 key");
			}

			return RemoveTerminatingItem(0, keysLength, keys);
		}

		public bool RemoveTerminatingItem(List<int> keys)
		{
			int keysLength = keys.Count;

			if (keysLength == 0)
			{
				keys = new List<int>() { default(int) };
				keysLength = 1;
			}

			return RemoveTerminatingItem(0, keysLength, keys);
		}

		public void AddTerminatingItem(TItem item, int keyLevel, int maxLevel, params int[] keys)
		{
			int key = keys[keyLevel];

			if (keyLevel == maxLevel - 1)
			{
				Populate.OnDemand(ref TerminatingItems);

				if (!TerminatingItems.ContainsKey(key))
				{
					// TODO: Think through race condition where key added twice
					try { TerminatingItems.Add(key, item); }
					catch { AddTerminatingItem(item, keyLevel, maxLevel, keys); }
					OnNotifyCollectionChanged(NotifyCollectionChangedAction.Add, item);
				}
				else
				{
					TItem old = default(TItem);
					if (TerminatingItems.TryGetValue(key, out old))
					{
						TerminatingItems[key] = item;

						if (CollectionChanged != null)
							OnNotifyCollectionChanged(NotifyCollectionChangedAction.Replace, new List<TItem>() { old, item });
					}
				}
			}
			else
			{
				Populate.OnDemand(ref ItemLookup);

				MultiLevelCustom<TItem> dictionary;

				if (!ItemLookup.TryGetValue(key, out dictionary))
				{
					MultiLevelCustom<TItem> newDictionary = new MultiLevelCustom<TItem>();

					// TODO: Think through race condition where key added twice
					try
					{
						ItemLookup.Add(key, newDictionary);
						newDictionary.AddTerminatingItem(item, keyLevel + 1, maxLevel, keys);
					}
					catch { AddTerminatingItem(item, keyLevel, maxLevel, keys); }
				}
				else { dictionary.AddTerminatingItem(item, keyLevel + 1, maxLevel, keys); }
			}

			AllTerminatingItemsListCache.SafeMarkDirty();
		}

		public void AddTerminatingItem(TItem item, int keyLevel, int maxLevel, List<int> keys)
		{
			int key = keys[keyLevel];

			if (keyLevel == maxLevel - 1)
			{
				Populate.OnDemand(ref TerminatingItems);

				if (!TerminatingItems.ContainsKey(key))
				{
					// TODO: Think through race condition where key added twice
					try { TerminatingItems.Add(key, item); }
					catch { AddTerminatingItem(item, keyLevel, maxLevel, keys); }
					OnNotifyCollectionChanged(NotifyCollectionChangedAction.Add, item);
				}
				else
				{
					TItem old = default(TItem);
					if (TerminatingItems.TryGetValue(key, out old))
					{
						TerminatingItems[key] = item;

						if (CollectionChanged != null)
							OnNotifyCollectionChanged(NotifyCollectionChangedAction.Replace, new List<TItem>() { old, item });
					}
				}
			}
			else
			{
				Populate.OnDemand(ref ItemLookup);

				MultiLevelCustom<TItem> dictionary;

				if (!ItemLookup.TryGetValue(key, out dictionary))
				{
					MultiLevelCustom<TItem> newDictionary = new MultiLevelCustom<TItem>();

					// TODO: Think through race condition where key added twice
					try
					{
						ItemLookup.Add(key, newDictionary);
						newDictionary.AddTerminatingItem(item, keyLevel + 1, maxLevel, keys);
					}
					catch { AddTerminatingItem(item, keyLevel, maxLevel, keys); }
				}
				else { dictionary.AddTerminatingItem(item, keyLevel + 1, maxLevel, keys); }
			}

			AllTerminatingItemsListCache.SafeMarkDirty();
		}

		public void AddTerminatingItem(TItem item, int x, int y, int z)
		{
			AddTerminatingItem(item, 0, 3, x, y, z);
		}

		public void AddTerminatingItem(TItem item, params int[] keys)
		{
			int keysLength = keys.Length;

			if (keysLength == 0)
			{
				keys = new int[] { default(int) };
				keysLength = 1;
				//throw new Exception("Must have at least 1 key");
			}

			AddTerminatingItem(item, 0, keysLength, keys);
		}

		public void AddTerminatingItem(TItem item, List<int> keys)
		{
			int keysLength = keys.Count;

			if (keysLength == 0)
			{
				keys = new List<int>() { default(int) };
				keysLength = 1;
			}

			AddTerminatingItem(item, 0, keysLength, keys);
		}

		public TItem FindItem(int keyLevel, int maxLevel, params int[] keys)
		{
			int key = keys[keyLevel];

			if (keyLevel == maxLevel - 1)
			{
				if (TerminatingItems != null)
				{
					TItem item;
					if (TerminatingItems.TryGetValue(key, out item))
						return item;
				}

				return default(TItem);
			}
			else
			{
				if (ItemLookup != null)
				{
					MultiLevelCustom<TItem> dictionary;

					if (ItemLookup.TryGetValue(key, out dictionary))
						return dictionary.FindItem(keyLevel + 1, maxLevel, keys);
				}

				return default(TItem);
			}
		}

		public TItem FindItem(int keyLevel, int maxLevel, List<int> keys)
		{
			int key = keys[keyLevel];

			if (keyLevel == maxLevel - 1)
			{
				if (TerminatingItems != null)
				{
					TItem item;
					if (TerminatingItems.TryGetValue(key, out item))
						return item;
				}

				return default(TItem);
			}
			else
			{
				if (ItemLookup != null)
				{
					MultiLevelCustom<TItem> dictionary;

					if (ItemLookup.TryGetValue(key, out dictionary))
						return dictionary.FindItem(keyLevel + 1, maxLevel, keys);
				}

				return default(TItem);
			}
		}

		public TItem FindItem(params int[] keys)
		{
			int keysLength = keys.Length;

			if (keysLength == 0)
			{
				keys = new int[] { default(int) };
				keysLength = 1;
			}

			return FindItem(0, keysLength, keys);
		}

		public TItem FindItem(int x, int y, int z)
		{
			return FindItem(0, 3, x, y, z);
		}

		public TItem FindItem(List<int> keys)
		{
			int keysLength = keys.Count;

			if (keysLength == 0)
			{
				keys = new List<int>() { default(int) };
				keysLength = 1;
			}

			return FindItem(0, keysLength, keys);
		}



		public void OnNotifyCollectionChanged(NotifyCollectionChangedAction action, IList<TItem> changedItems)
		{
			if (CollectionChanged != null)
			{
				switch (action)
				{
					case NotifyCollectionChangedAction.Add:
						CollectionChanged(this, new NotifyCollectionChangedEventArgsEx<TItem>(action, changedItems));
						break;
					case NotifyCollectionChangedAction.Remove:
						CollectionChanged(this, new NotifyCollectionChangedEventArgsEx<TItem>(action, changedItems));
						break;
					case NotifyCollectionChangedAction.Replace:
						CollectionChanged(this, new NotifyCollectionChangedEventArgsEx<TItem>(action, changedItems[0], changedItems[1]));
						break;
					case NotifyCollectionChangedAction.Reset:
						CollectionChanged(this, new NotifyCollectionChangedEventArgsEx<TItem>(action));
						break;
				}
			}
		}

		public void OnNotifyCollectionChanged(NotifyCollectionChangedAction action, TItem changedItem)
		{
			if (CollectionChanged != null)
				OnNotifyCollectionChanged(action, new List<TItem>() { changedItem });
		}

		#region INotifyCollectionChanged Members

#if NO_SILVERLIGHT
        [field: NonSerialized]
        public event NotifyCollectionChangedEventHandler CollectionChanged;
#else
        [field: NonSerialized]
        public event EventHandler<NotifyCollectionChangedEventArgsEx<TItem>> CollectionChanged;
#endif

		#endregion
	}
}